iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0

Day 6 我們利用 Database Index 改善 Query Performance。

但假設熱門 Product Page 每秒有:

100,000 Requests

即使 Query 只需要 1 ms,如果每個 Request 都直接打 Database:

100,000 Requests
       ↓
100,000 Database Queries

Database 還是可能成為 Bottleneck。

如果 Product Data 短時間內根本沒有改變,我們真的需要每次重新 Query Database 嗎?

這就是今天的主題:

Cache


Cache 是什麼?

Cache 可以理解成:

把經常使用的資料暫時放在更快、距離 Application 更近的地方。

原本:

User → Backend → Database

加入 Cache:

User → Backend → Cache → Database

Backend 先問 Cache:

有資料?
├── Yes → 直接回傳
└── No  → Query Database

Cache Hit 與 Cache Miss

假設:

GET /products/123

Cache 已有:

product:123

Backend 直接取得資料:

User → Backend → Cache ✅ → Response

這叫:

Cache Hit

如果 Cache 沒有:

User
 ↓
Backend
 ↓
Cache ❌
 ↓
Database
 ↓
取得資料
 ↓
寫入 Cache
 ↓
Response

這叫:

Cache Miss

下一次相同 Request 就可能變成 Cache Hit。


Cache Hit Rate

假設:

100 Requests

90 → Cache Hit
10 → Cache Miss

那麼:

Cache Hit Rate = 90%

越多 Request 可以直接從 Cache 取得資料,通常就能減少 Database Load。

但仍要考慮:

Data Freshness
Memory
Consistency
Eviction

Redis 是什麼?

Redis 常被用作 In-Memory Data Store。

今天可以先把它理解成:

Key → Value

例如:

product:123
      ↓
{
  "id": 123,
  "name": "MacBook",
  "price": 999
}

Redis 常見用途:

Cache
Session Store
Rate Limiter
Counter
Leaderboard

Day 7 先專注:

Redis as Cache


Cache-Aside Pattern

非常常見的 Cache Pattern:

Cache-Aside

流程:

1. Application 讀 Cache
2. Cache Hit → Return
3. Cache Miss → Query Database
4. Write Cache
5. Return
                ┌── Hit ─────────→ Response
User → Backend ─┤
                └── Miss
                     ↓
                  Database
                     ↓
                 Set Cache
                     ↓
                  Response

Pseudo Code:

def get_product(product_id):
    key = f"product:{product_id}"

    product = cache.get(key)

    if product is not None:
        return product

    product = database.get_product(product_id)

    cache.set(key, product)

    return product

最大問題:Stale Data

假設:

Database → Price = $999
Cache    → Price = $999

Admin 更新 Database:

Database → Price = $899

但是 Cache 還是:

Cache → Price = $999

User 讀 Cache 就會看到舊資料。

這叫:

Stale Data

因此 Cache 真正困難的問題之一是:

Cache Invalidation

也就是:

什麼時候要讓舊 Cache 失效?

TTL

最簡單的方法之一:

TTL(Time To Live)

例如:

product:123
TTL = 60 seconds

60 秒後:

Cache Expired
      ↓
Next Request
      ↓
Cache Miss
      ↓
Database
      ↓
Rebuild Cache

Trade-off:

Short TTL
→ Fresher Data
→ More DB Load
Long TTL
→ Less DB Load
→ More Stale Data Risk

所以 TTL 沒有一個固定的最佳答案。


Update Database 後刪除 Cache

Cache-Aside 常見做法:

1. Update Database
2. Invalidate / Delete Cache

下一次 Read:

Cache Miss
 ↓
Database
 ↓
Rebuild Cache

概念:

Write DB
   ↓
Delete Cache
   ↓
Next Read
   ↓
Repopulate Cache

Database 通常仍然是:

Source of Truth

而 Cache 是:

Temporary Copy


為什麼不是每次都直接 Update Cache?

假設 Cached Result 來自:

products
inventory
discounts
reviews

每次任何 Table 修改都同步重建 Cache,可能增加複雜度。

因此一種常見思路是:

Update Source of Truth
        ↓
Invalidate Cache
        ↓
需要時重新建立

但實際系統仍需要處理 Concurrency 與 Consistency 問題。


什麼資料適合 Cache?

通常比較適合:

Read Frequently
Change Infrequently
Expensive to Query
Expensive to Compute

例如:

Product Details
Popular Posts
User Profile
Configuration
Aggregated Statistics

例如:

Product
Read 10,000 times/sec
Update 2 times/day

就是很值得考慮 Cache 的情境。


哪些資料要更小心?

例如:

Bank Balance
Inventory
Payment Status

對:

Freshness
Consistency
Correctness

要求較高。

不是完全不能 Cache,而是必須先問:

這份資料允許多舊?

例如 Inventory 顯示:

Only 1 left

可以允許短時間 Stale。

但真正 Checkout 時,不能只相信可能過期的 Cache。

仍可能需要:

Database Transaction
Atomic Operation
Locking

避免 Overselling。


Cache Eviction

Memory 不可能無限大。

例如:

Redis Memory = 10 GB
Cache Data   = 20 GB

一定需要移除部分資料。

這叫:

Cache Eviction

常見概念:

LRU
LFU
TTL

LRU

Least Recently Used

最近最久沒被使用的資料優先移除。

LFU

Least Frequently Used

使用頻率最低的資料優先移除。

簡單記:

LRU → Recency
LFU → Frequency

Cache Stampede

假設熱門:

product:123

每秒有:

100,000 Requests

突然:

TTL Expired

大量 Request 同時 Cache Miss:

100,000 Requests
       ↓
    Database
       😵

這叫:

Cache Stampede

可能的處理方式:

Lock / Single Flight
TTL Random Jitter
Background Refresh

例如 Single Flight:

1000 Cache Misses
       ↓
只有一個 Request Query DB
       ↓
Rebuild Cache
       ↓
其他 Request 等待 / 共用結果

Cache Penetration

假設一直查不存在的 Product:

GET /products/999999999

流程每次都是:

Cache Miss
 ↓
Database
 ↓
Not Found

下一次又重新打 Database。

大量不存在的 Key 可能穿過 Cache 持續打到 Database。

這常被稱為:

Cache Penetration

一種做法:

Cache Negative Result

例如:

product:999999999
→ NOT_FOUND
→ TTL 30 sec

更進階也可能使用:

Bloom Filter

Cache Avalanche

如果大量 Keys 在接近的時間一起 Expire,或者整個 Cache Cluster 故障:

Cache unavailable
       ↓
大量 Requests → Database
       ↓
Database Overload

這類情況常被稱為:

Cache Avalanche

所以值得分清楚:

Stampede
→ 熱門 Cache Miss 時大量 Request 同時重建

Penetration
→ 不存在的資料一直穿透 Cache

Avalanche
→ 大量 Cache 同時失效 / Cache System Failure

Hot Key

假設某個 Key:

product:123

突然爆紅。

所有 Request 都集中到同一個 Key。

即使整個 Redis Cluster Capacity 很高,某一個 Node 仍可能因 Hot Key 承受極大 Traffic。

所以:

平均 Traffic 正常,不代表 Traffic Distribution 正常。


Local Cache vs Distributed Cache

Cache 不一定是 Redis。

Local Cache

Backend #1
   ↓
Local Memory

優點:

Very Fast
No Network Call

但是多台 Server:

Server #1 → Price $999
Server #2 → Price $899
Server #3 → No Cache

可能出現不同步。

Distributed Cache

例如 Redis:

Server #1 ─┐
Server #2 ─┼── Redis
Server #3 ─┘

所有 Backend 共用 Cache。

但代價是:

Network Call
Infrastructure
Scaling
Monitoring
High Availability

因此仍然是 Trade-off。


Cache 掛掉會發生什麼?

正常:

100,000 Requests
       ↓
90,000 Cache Hits
10,000 DB Queries

Redis 掛掉:

100,000 Requests
       ↓
100,000 DB Queries

Database 可能瞬間被打爆。

這可能形成:

Cascading Failure

也就是:

Cache Failure
     ↓
DB Load ↑
     ↓
Database Failure
     ↓
Application Failure

可能的保護方向:

Redis Replication / Cluster
Timeout
Circuit Breaker
Rate Limiting
Graceful Degradation
Capacity Planning

台積 IT 面試情境:熱門 Product Page

假設:

Product Page 每秒有 100,000 Requests,但 Product Data 一天只更新幾次,怎麼改善?

分析:

Read Heavy
Same Data
Changes Infrequently

可以考慮:

User
 ↓
Load Balancer
 ↓
Backend
 ↓
Redis
 ↓
Database

Read:

GET product:123
      ↓
Cache Hit?
 ├── Yes → Return
 └── No
      ↓
   Database
      ↓
   Set Cache
      ↓
    Return

Write:

Update Product
      ↓
Update Database
      ↓
Invalidate Cache

面試官還可能繼續問:

TTL 設多久?
Cache 和 DB 不一致怎麼辦?
Redis 掛掉怎麼辦?
Hot Key Expire 怎麼辦?

這時就可以延伸:

TTL
Invalidation
Stampede
High Availability
Fallback
Rate Limiting

System Design Trade-off

沒有 Cache:

Backend
 ↓
Database

優點:

Simple Architecture
Consistency Easier

缺點:

Higher DB Load
Higher Latency

加入 Cache:

Backend
 ↓
Cache
 ↓
Database

優點:

Lower Latency
Lower Database Load
Higher Read Throughput

缺點:

Cache Invalidation
Stale Data
More Infrastructure
More Failure Modes

所以:

Cache 是用 Complexity 換 Performance。


台積 IT 面試準備 Checkpoint

1. Cache 是什麼?

2. Cache Hit / Cache Miss 是什麼?

3. Cache Hit Rate 是什麼?

4. Redis 為什麼適合做 Cache?

5. Cache-Aside Pattern 是什麼?

6. TTL 是什麼?

7. TTL 太長 / 太短有什麼 Trade-off?

8. Cache Invalidation 是什麼?

9. DB Update 後 Cache 怎麼處理?

10. Cache Stampede 是什麼?

11. Cache Penetration 是什麼?

12. Cache Avalanche 是什麼?

13. Hot Key 是什麼?

14. LRU vs LFU?

15. Local Cache vs Distributed Cache?

16. Redis 掛掉會怎麼樣?

17. 怎麼避免 Cache Failure 把 Database 一起打掛?

18. 每秒 100,000 Reads、每天更新幾次的 Product Page 怎麼設計?

19. Inventory 可以 Cache 嗎?

20. Cache 和 Database 哪個是 Source of Truth?

如果能用自己的話回答這些問題,就不只是知道:

Redis = Cache

而是開始理解:

Performance
Consistency
Availability
Failure
Trade-off

今天學到了什麼?

Cache 核心:

Frequently Accessed Data
        ↓
      Cache
        ↓
Reduce Latency
Reduce Database Load

基本流程:

Request
 ↓
Cache
 ↓
Hit?
 ├── Yes → Return
 └── No → Database
            ↓
         Set Cache
            ↓
          Return

但 Cache 也帶來:

Stale Data
Invalidation
TTL
Eviction
Stampede
Penetration
Avalanche
Hot Key
Cache Failure

最重要的一句:

Cache 不是免費的 Performance。它用更多 System Complexity,換取更低的 Latency 與更少的 Database Load。


下一篇

有些系統:

Read Traffic >> Write Traffic

即使加入 Cache,Cache Miss 仍然需要 Database Read。

那能不能把 Database 複製成多份,讓更多 Database 一起處理 Read Traffic?

下一篇:

Day 8|Database Replication:一台 Database 不夠讀怎麼辦?

會開始了解:

Primary
Replica
Read Replica
Replication Lag
Read / Write Splitting
Failover

以及:

Primary 掛掉怎麼辦?
Replica 資料一定最新嗎?
剛更新 Profile 為什麼可能看到舊資料?
Replication vs Sharding 有什麼不同?

Architecture 將繼續從:

Backend → Cache → Database

往:

                 ┌── Read Replica #1
Backend → Cache ─┼── Read Replica #2
        │        └── Read Replica #3
        │
        └──────────→ Primary Database

繼續處理下一個 Bottleneck。


上一篇
# Day 6|Database Index:為什麼加一個 Index,Query 可以快這麼多?
下一篇
# Day 8|Database Replication:一台 Database 不夠讀怎麼辦?
系列文
30 天從 Full-Stack Engineer 進化到 System Design:從 0 設計可支撐百萬使用者的系統9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言